Conversation
Add GET/DELETE /admin/api/v1/users/me/2fa/devices[/{id}] so a user can
list their active trusted devices and revoke one or all of them
(SDS idp-mfa.md 5.6, CU-86bc647cd).
- Revocation is scoped by owner: another user's device id returns 404.
- Revoking an already revoked or expired device is a 204 no-op and is
not audited; revoke-all logs one device_revoked event per device it
actually revokes and none when there are no active devices.
- Audit runs best-effort after the revocation commits, following
RecoveryCodeService: audit_service->log() opens its own transaction,
which cannot be nested.
- The device-trust cookie is expired when the revoked device is the one
the request came from.
- The list never exposes device_identifier or user_agent and flags the
current device via is_current; it does not touch last_seen_at.
- Replace the bulk revokeAllForUser() UPDATE with a per-entity revoke of
active devices so each revoked device can be audited.
Add a Trusted Devices subsection to the profile page (SDS idp-mfa.md 5.5, CU-86bc647cd), shown only when 2FA applies to the user. It lists the user's active trusted devices (name, IP, trusted, last seen, expiry), labels the current one, and lets the user revoke a single device or all of them (with a confirmation), updating the table in place. Buttons are plain onClick handlers since the profile page is a single <form>. Expose the three device endpoints on window from profile.blade.php and add getTrustedDevices / revokeTrustedDevice / revokeAllTrustedDevices helpers, covered by Jest tests for the component and the unmocked request layer.
|
Navigate logical layers of code changes, visualize relationships, and explore their blast radius. No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Advanced Run ID: 📒 Files selected for processing (18)
Included review availability: This review used your included allowance. Your plan provides up to 1 included review per hour; 0 remain after this review. 📝 WalkthroughWalkthroughThe change adds authenticated endpoints to list and revoke trusted devices. It adds service-level ownership checks and revocation auditing, then exposes device status and revocation controls on the profile page when two-factor authentication is enabled. ChangesTrusted device management
Priority: ⬇️ Low Estimated code review effort: 4 (Complex) | ~45 minutes Change: Feature Sequence Diagram(s)sequenceDiagram
participant TrustedDevicesSection
participant UserApiController
participant DeviceTrustService
participant TrustedDeviceRepository
participant AuditLogger
TrustedDevicesSection->>UserApiController: Request device list or revocation
UserApiController->>DeviceTrustService: Retrieve or revoke devices for authenticated user
DeviceTrustService->>TrustedDeviceRepository: Read active devices or owner-scoped device
TrustedDeviceRepository-->>DeviceTrustService: Return matching device records
DeviceTrustService->>TrustedDeviceRepository: Persist device revocations
DeviceTrustService->>AuditLogger: Attempt per-device revocation audit
DeviceTrustService-->>UserApiController: Return devices or revocation result
UserApiController-->>TrustedDevicesSection: Return serialized devices or deleted response
Suggested reviewers: Merge Risk: ⚪ Minimal · up to The reviewed trusted-device listing and revocation paths have no established issue requiring a fix before merge. The reported end-to-end test failure was not independently established as a regression from this change. 🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches 💡 2📝 Generate docstrings 💡
🛠️ Fix failing CI checks 💡
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
|
📘 OpenAPI / Swagger preview ➡️ https://OpenStackweb.github.io/openstackid/openapi/pr-160/ This page is automatically updated on each push to this PR. |
|
@smarcet the e2e job on this PR fails, but the failure is not caused by this PR. It comes from the base branch: Symptom: every login e2e test ( Root cause (from the Playwright trace artifact): the page is served from
- if (Config::get('server.ssl_enabled', false))
+ if (!App::isLocal())
URL::forceScheme('https');I understand the Some options that would work for both:
Which one do you prefer? I can open a PR against the base branch.
The other checks on this PR are green, including |
ref https://app.clickup.com/t/86bc647cd (SDS idp-mfa.md 5.5 / 5.6)
Summary
Users could mark a device as trusted when completing a 2FA challenge, but had no way to see or revoke those devices: a lost or shared device kept bypassing the challenge until the cookie expired (30 days by default). This adds self-service listing and revocation, on the API and on the profile page.
Backend
New routes in the existing
admin/api/v1→users/megroup (session-authenticated,ssl+auth):GET/admin/api/v1/users/me/2fa/devices200{data: [...]}with the caller's active devicesDELETE/admin/api/v1/users/me/2fa/devices/{id}204(csrf)DELETE/admin/api/v1/users/me/2fa/devices204(csrf)UserTrustedDeviceSerializerreturnsid,device_name,ip_address,trusted_at,expires_at,last_seen_atandis_current(the device whose cookie came with the request). It never exposesdevice_identifieroruser_agent, and it reads through the repository, so it does not refreshlast_seen_atthe wayisDeviceTrusted()does.IUserTrustedDeviceRepository::getByIdAndUser()scopes the lookup by owner. Another user's device id is indistinguishable from a missing one (404), and the row is left untouched.device_revokedis logged only on a realfalse → truetransition ofis_revoked, one row per device.204no-op with no audit row.queueDeviceTrustCookie()(MFACookieManager::expireDeviceTrustCookie()), so the next login is challenged. The session itself stays active.removeTrustedDevices()now revokes active devices entity by entity inside a transaction and returns them. The old bulkrevokeAllForUser()DQLUPDATEis removed; its only caller was this method, and it could not tell which devices it revoked.Frontend
TrustedDevicesSection(resources/js/components/trusted_devices_section.js), mounted under the 2FA section of the profile page and shown only whentwoFactorEnabled:onClick(the profile page is a single<form>).windowfromprofile.blade.php; fetch helpers inresources/js/profile/actions.js.Deviations from the SDS / ticket
Api\UserApiControllerunderusers/me/2fainroutes/web.php, next to2fa/enableandrecovery-codes/regenerate, instead of the SDS's newApi\UserTwoFactorApiControllerinroutes/api.php. This avoids splitting the same resource across two controllers.302to/auth/login, not403: the group'sauthmiddleware rejects guests before the controller runs, same as the sibling routes. The ticket's acceptance criterion was updated accordingly.ITwoFactorAuditService::log()opens its own transaction, and nestingDoctrineTransactionService::transaction()calls is unsafe (seeRecoveryCodeService::enableTwoFactorAndGenerateCodes()). This follows the same pattern: an audit failure is logged as a warning and does not fail an already-committed revocation.flow = mfa; the actual session value is2fa, and that is what the test asserts.fn_device_trust; the code keeps readingconfig('two_factor.cookie_name').Testing
tests/TrustedDevicesApiTest.php(new, 15 tests):is_currentandlast_seen_atuntouched;404and unknown id404;302on all three routes.tests/DeviceTrustServiceTest.php: updated for the newremoveTrustedDevices()behaviour; new unit tests forrevokeTrustedDevice()and for surviving an audit failure.tests/TwoFactorProfileRoutesCsrfTest.php: both DELETE routes added.tests/js/components/trusted_devices_section.test.js(render, current-device label, empty state, revoke, failed revoke, revoke-all confirm/cancel, no submit buttons) andtests/js/profile/actions.test.js(the three helpers against the unmocked request layer).Results:
DeviceTrustServiceTest,TrustedDevicesApiTest,TwoFactorProfileRoutesCsrfTest,TwoFactorRepositoriesTest,TwoFactorLoginFlowTest,RecoveryCodeRegenerationTest,OAuth2NativeMFALoginFlowTest,VerifyOTPChallengeTest,AuthServiceLoginUserTestand thetests/unitMFA / 2FA tests. The full PHPUnit suite was not run.yarn test:unitgreen, 13 suites / 76 tests.Out of scope
2FA disable toggle and method selection, phone verification, admin-side device management, and friendly user-agent parsing for
device_name.Summary by CodeRabbit